-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat: zod v4 #869
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: zod v4 #869
Conversation
src/server/mcp.ts
Outdated
enum?: string[]; | ||
}; | ||
|
||
function zodToJsonSchema(schema: ZodObject<ZodRawShape>): JsonSchema { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still need to think about this PR more broadly, e.g. what the impacts of this potentially breaking change will be.
But I think for this we could use https://zod.dev/json-schema instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to give it a shot today!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@domdomegg
So I definitely just had a misunderstanding of how the new toJSONSchema
method from Zod 4 worked -- just updated and stripped out the custom code in favor of that. Tests passed without issue. Check it out!
Thanks for working on this. We are preparing for Typescript V2, this potentially can be included there |
Let me know if I can help! @ihrpr |
{ | ||
interval: z.number().describe('Interval in milliseconds between notifications').default(1000), | ||
count: z.number().describe('Number of notifications to send').default(10), | ||
interval: z.number().describe('Interval in milliseconds between notifications').prefault(1000), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is prefault
preferred in this case?
as far as I understand from https://zod.dev/v4/changelog#default-updates it's about expected input type (in their example it's a string) and output type (in their example the string goes through transform to output a number)
looks like here default will work as well since input type is same as output type - number
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah we can use default! let me get it updated. the util I used defaults to prefault
to preserve default functionality so I left it as is.
@avivnakar / all Updated with main, resolved conflicts, reverted to use |
src/server/mcp.ts
Outdated
// Lightweight replacement for zod-to-json-schema tailored for this SDK's needs. | ||
// Produces a minimal JSON Schema subset: { type: "object", properties, required? } | ||
// Supports primitives, enums, and nested objects. Sufficient for our tool list output tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
obsolete comment
Hi @dclark27, thanks for your work on this! I wonder if the v3 & v4 compatibility can be tested (and maybe documented)? Maybe adding v3 as a dev dependency and have some tests explicitly importing the /v3 paths? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, with some reservations:
- Can we provide some testing of backwards compatibility (client / server code w/ v3)
- Wonder how this will interplay w/ #792 (what is the v4 equiv. of strip? ideally we'll want to merge the two PRs to see how they mesh)
Just thought I'd let you know after reading the guide here (https://zod.dev/library-authors?id=how-to-support-zod-4) for library authors, you shouldn't be importing from |
Two ways forward here, I can adjust this PR to include backward compatibility with v3 and go adjust his PR after it merges, or I can merge his branch into mine and tackle holistically once theirs is merged. We can use strictObject and looseObject for interplay it looks like. https://zod.dev/v4/changelog?id=deprecates-strict-and-passthrough For now I will start with backward compatibility in this branch and see what progress is made with #792 unless you have a strong preference. |
cc: @ochafik @domdomegg I have updated this branch with main and resolved conflicts. There is a blocking issue though preventing us from supporting Zod v3 and v4, which is that zod-to-json-schema does not allow 3.25 as a peer dependency.
Check out my changes here with backwards compatibility changes and extra regression tests for v3 and v4: socotra@c1e5d38 We might be blocked until StefanTerdell/zod-to-json-schema#180 is merged. Others have published around them but I'd rather find another option. |
Could the migration to zod v4 replace the usage of Ajv since Zod v4 supports json schema conversion natively? |
Have you by any chance published this branch on npm? |
Yep!
|
Thank you! |
0845a57
to
c94ba4b
Compare
); | ||
complete: CompleteCallback<T> | ||
): T & { | ||
_def: (T extends { _def: infer D } ? D : unknown) & CompletableDef<T>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first of all I want to claim that I am not a part of the mcp organization so I hope I am not a blocking phase for this feature. I really am anticipating for the SDK to support zod v4 and I want to thank you for your time and effort!
I took a little more time to further learn about zod and the SDK so I can provide some meaningful insights if possible
according to https://zod.dev/library-authors?id=how-to-support-zod-3-and-zod-4-simultaneously you can support both z3 and z4 which can make this feature (in case that the maintainers of this repo are willing) a non-breaking change so it can be part of sdk v1 rather than v2. I suggest asking the maintainers before re-implementing the whole PR of course!
besides backward compatibility, it looks like _def
is a zod v3 style whereas in zod v4 it should be ._zod.def. I really am not sure about that, I assume you already stumbled upon this and I just am missing something but I'd love to hear what you have to say about this.
one thing that I am not sure about is your choice to as unknown as
and the usage as
in general. I suggest adding a comment that explains why this kind of type manipulation is required rather than using type guards (using simple conditions/ custom type predicates with is
or maybe using zod) that will explain intentions better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also am not sure about the completable
feature and have never used it before
const defLike = (field as unknown as { _def?: { typeName?: unknown } }) | ||
._def; | ||
if (!defLike || defLike.typeName !== McpZodTypeKind.Completable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, not sure why as unknown as
is required, I also suggest to use a helper function, something like
const defLike = (field as unknown as { _def?: { typeName?: unknown } }) | |
._def; | |
if (!defLike || defLike.typeName !== McpZodTypeKind.Completable) { | |
if (notCompletable(field)) { |
could look nicer
dpop_bound_access_tokens_required: z.boolean().optional(), | ||
}) | ||
.passthrough(); | ||
export const OAuthProtectedResourceMetadataSchema = z.object({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think looseObject
replaces the removed passthrough
export const OAuthProtectedResourceMetadataSchema = z.object({ | |
export const OAuthProtectedResourceMetadataSchema = z.looseObject({ |
refresh_token: z.string().optional(), | ||
}) | ||
.strip(); | ||
export const OAuthTokensSchema = z.object({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://zod.dev/v4/changelog#deprecates-strict-and-passthrough
export const OAuthTokensSchema = z.object({ | |
export const OAuthTokensSchema = z.strictObject({ |
also in 184 194 211 219
Upgrade to Zod v4 for compatibility with v3 and v4.
Note, it wasn't clear which formatter to use so some commas changed and some lines I did not intend to.
Motivation and Context
This has been a blocking issue for my team and many others. When versions mismatch across dependencies with older Zod versions, memory leaks become present and can slow or stall development.
This also will help unblock downstream dependencies, such as with vercel's AI SDK and mcp-tools.
#494
vercel/ai#7160
vercel/mcp-handler#93
How Has This Been Tested?
Breaking Changes
Users will need to use Zod 3.25.76+ or Zod 4 and import from either /v3 or /v4.
Types of changes
Checklist
Additional context